# /**
# * Copyright (c) 2021-2025, God's harbor
# * All rights reserved.
# * @file    mark_files.sh
# * @brief   标记规范错误和常见代码缺陷
# * @author  Jason Zhou, yzfcer@163.com
# * @version 1.0
# * @date    2021-11-25
# */
#!/bin/sh

xspace0x="\s*"       # >=0或多个空格
xspace1x="\s\+"      # >=1个空格
xnspace0x="[\S]*"    # >=0非空格
xnspace1x="[\S]\+"   # >=1非空格

xlbrackets0x="[(]*"  # >0左括号
xrbrackets0x="[)]*"  # >0右括号
xlbrackets1x="[(]\+" # >1左括号
xrbrackets1x="[)]\+" # >1右括号

xstar0x="${xspace0x}\**${xspace0x}" #>0个*
xstar1x="${xspace0x}\*\+${xspace0x}" #>1个*

xoper1="\(++\|\-\-\)"
xoper2="\(=\|+\|\-\|\/\|==\|+=\|\-=\|*=\|\/=\||=\|&=\|\^=\|!=\)"
xoper="\(${xoper1}\|${xoper2}\)"

xmacro="[A-Z][A-Z_0-9]\?" #宏定义
xdecimal="[0-9]\+" #10进制
xhex="0[x, X][0-9A-Fa-f]\+" #16进制
xoctal="0[0-7]\+" #8进制
xbinary="[0-1]\+[b, B]" #2进制
xnum="\(${xdecimal}\|${xhex}\|${xoctal}\|${xbinary}\)" #数字定义
xconst="\(${xnum}\|${xmacro}\)" #常量定义

xsynbol="\(\<unsigned\>${xspace1x}\)\?" #unsigned+空格(可以没有)
xtype1="${xsynbol}\(\<char\>\|\<short\>\|\<int\>\|\<long\>\)" #原始数据类型
xtype2="\<x[u]\?int\(8\|16\|32\|64\)_t\>" #锐明数据类型
xtype3="\<x\(single\|double\|byte\|pvoid\|handle\|time32\|time64\)_t\>" #锐明数据类型
xtype4="\<void\>"
xbasetype="${xsynbol}\(${xtype1}\|${xtype2}\|${xtype3}\|${xtype4}\)\+${xspace0x}" #基本数据类型
xtype5="\<e[A-Z][A-Za-z_0-9_]\*\>" #锐明数据类型
xtype6="\<[A-Z][A-Za-z_0-9_]\*_t\>" #锐明数据类型
xtype7="\<[A-Z][A-Za-z_0-9_]\*_u\>" #锐明数据类型
xcomtype="\(${xtype5}\|${xtype6}\|${xtype7}\)"
xtype="${xsynbol}\(${xtype1}\|${xtype2}\|${xtype3}\|${xtype4}\|${xtype5}\|${xtype6}\|${xtype7}\)\+${xspace0x}" #数据类型

xerrvar1="[A-Z_][a-zA-Z_0-9]*" #大写字母开头
xerrvar2="[A-Za-fg-ln-rt-z_0-9_]\+[a-zA-Z0-9]*[_]\+[a-zA-Z0-9]*" #变量中有下划线, 但成员变量，静态变量，全局变量例外
xerrvar="\(${xerrvar1}\|${xerrvar2}\)\+" #错误变量格式
xokvar="\<[a-z][a-zA-Z_0-9]*\>" #小写字母开头正确
xvar="\(${xerrvar}\|${xokvar}\)" #变量格式
# xvartail1="\(${xspace0x};\)"
# xvartail2="\(${xspace0x}=\)"
# xvartail="\(${xvartail1}\|${xvartail2}\)"
xvalue="\(${xvar}\|${xconst}\)"

xokvardef="${xtype}${xspace1x}${xokvar}" #正确变量定义
xerrvardef="${xtype}${xspace1x}${xerrvar}" #错误变量定义

# xfunctype="\(${xtype}${xstar0x}\)"
xerrfunc1="[a-z_][a-zA-Z_0-9]*\>[^()]*)("
xerrfunc2="[a-z_][a-zA-Z_0-9]*\>\s**\s*("
xfunc3="[a-zA-Z_][a-zA-Z_0-9]*[_]\+[a-zA-Z_0-9]*\>[^()]*)("
xfunc4="[a-zA-Z_][a-zA-Z_0-9]*[_]\+[a-zA-Z_0-9]*\>\s**\s*("
xfunc="\(${xfunc1}\|${xfunc2}\|${xfunc3}\|${xfunc4}\)"
xfuncdef="${xfunctype}${xfunc}"

#错误信息暂存变量
ErrMsg=""
FileCnt=0
TotLineCount=0
commentStat=0
newStr=""
IsPatch=0

# remove //
function handleCommentStat0()
{
    lineStr=$1
    newStr=${lineStr%%//*}
}

# remove /*
function handleCommentStat1()
{
    # echo "stat 1"
    # commentStat=2
    lineStr=$1
    pos1=$(echo $1|awk '{print index($1, "/*")}')
    if [ $pos1 != "0" ]
    then
        subStr=${lineStr:0:$((pos1-1))}
        echo "lineStr=${lineStr}"
        echo "subStr=${subStr}"
        # echo "pos1=${pos1}"
    fi
}

#remove */
function handleCommentStat2()
{
    echo "stat 2"
    commentStat=0
    lineStr=$1
    pos1=$(echo $1|awk '{print index($1, "//")}')
    if [ $pos1 != "0" ]
    then
        echo "pos1=${pos1}"
    fi
}

# remove +++
function RemoveInvalidPatchString()
{
    lineStr=$1
    # echo $lineStr
    result=$(echo "$lineStr" | grep "^+")
    # echo "result1=${result}"
    if [ "x$result" == "x" ]
    then
        newStr=""
        # echo "return result1"
        # echo $newStr
        return
    fi
    result=$(echo "$lineStr" | grep "^+++")
    # echo "result2=${result}"
    if [ "x${result}" != "x" ]
    then
        newStr=""
        # echo "return result2"
        return
    fi
    newStr=${lineStr:1}
    echo "newStr=$lineStr"
}

function RemoveComment()
{
    lineStr=$1
    case ${commentStat} in
        0)
            handleCommentStat0 ${lineStr}
            ;;
        1)
            handleCommentStat1 ${lineStr}
            ;;
        2)
            handleCommentStat2 ${lineStr}
            ;;
        default)
            commentStat=0
            ;;
    esac
}

function CheckString()
{
    lineStr=$1
    keyStr=$2
    errStr=$3
    resStr=$(echo -e "${lineStr}" | grep "${keyStr}")
    if [ x"${resStr}" != "x" ]
    then
        if [ x"$ErrMsg" == "x" ]
        then
            ErrMsg+=${errStr}
        else
            result=$(echo ${ErrMsg} | grep "${errStr}")
            if [ x${result} != "x" ]
            then
                return
            fi

            tmpMsg="$ErrMsg, ${errStr}"
            # echo ${tmpMsg}
            ErrMsg=${tmpMsg}
        fi
    fi
}

function CheckSkip()
{
    lineStr=$1
    keyStr=$2
    errStr=$3
    resStr=$(echo -e "${lineStr}" | grep "${keyStr}")
    if [ x"${resStr}" != "x" ]
    then
        if [ x"$ErrMsg" == "x" ]
        then
            return 0
        else
            return 1
        fi
    fi
}

function CheckAllStr()
{
    lineStr=$1
    CheckString "${lineStr}" "\<if(" "if后面加空格"
    CheckString "${lineStr}" "\<for(" "for后面加空格"
    CheckString "${lineStr}" "\<while(" "while后面加空格"
    CheckString "${lineStr}" "\<switch(" "switch后面加空格"
    # CheckString "${lineStr}" "${xnspace1x}${xspace0x}{" "大括号独占一行"
    # CheckString "${lineStr}" "{${xspace0x}${xnspace1x}" "大括号独占一行"

    brackets="\((\ \|\ )\)"
    CheckString "${lineStr}" "${brackets}" "括号内部紧邻括号前后不加空格"
    CheckString "${lineStr}" "${xtype1}" "不能用C语言原生数据类型"
    CheckString "${lineStr}" "strcpy" "用strncpy替代"
    CheckString "${lineStr}" "strlen" "用strnlen替代"
    CheckString "${lineStr}" "sprintf" "用snprintf替代"
    CheckString "${lineStr}" ";${xvar}" "分号后面要加空格"

    keyOneOper="\(${xoper1}${xspace1x}${xvar}\|${xvar}${xspace1x}${xoper1}\)"
    CheckString "${lineStr}" "${keyOneOper}" "一目运算符前后不加空格"

    keyTwoOper="\(${xvalue}${xoper2}${xspace0x}${xvalue}\|${xvalue}${xspace0x}${xoper2}${xvalue}\)"
    CheckString "${lineStr}" "${keyTwoOper}" "二目运算符前后加空格"

    CheckString "${lineStr}" "${xtype}${xspace1x}${xerrvar};" "变量定义格式错误"
    # CheckString "${lineStr}" "=${xspace0x}{${xspace0x}0${xspace0x}}${xspace0x};" "用memset代替{0}来初始化"

    CheckString "${lineStr}" "${xvar}${xspace0x}==${xspace0x}${xconst}" "双等号常量在前"

    CheckString "${lineStr}" "#define${xspace1x}${xvar}${xspace1x}${xnum}" "宏定义要加括号"
    CheckString "${lineStr}" ",\S" "逗号后面加空格"

    memStr="\(memset\|memcpy\|strncpy\|strncmp\)"
    CheckString "${lineStr}" "${memStr}" "[note]注意内存边界"
    keyTypedef="\(typedef${xspace1x}struct\|typedef${xspace1x}union\)"
    CheckString "${lineStr}" "${keyTypedef}" "[note]注意自定义数据类型字节对齐"
    CheckString "${lineStr}" "alloc" "[note]注意需要判定返回值，在哪里释放"
    CheckString "${lineStr}" "\(printf\|scanf\|RM_CBB_LOG\)" "注意检查格式化的参数个数，参数类型是否一致"
    CheckString "${lineStr}" "\(\<free\>\|\<delete\>\)" "[note]注意需要对指针置空，防止后面误用"
    CheckString "${lineStr}" "${xnspace1x}?${xnspace1x}*${xnspace1x}:${xnspace1x}" "[note]注意三目运算符前后要加空格"
    CheckString "${lineStr}" "typedef${xspace1x}struct" "[note]注意结构体成员要按照字节对齐"
    CheckString "${lineStr}" "^${xspace1x}struct${xspace1x}${xtype}" "[note]注意结构体成员要按照字节对齐"
}

function CheckHeaderStr()
{
    lineStr=$1
    CheckString "${lineStr}" "}${xspace0x}^\(${xcomtype}\)${xspace0x};"
}

function CheckSourceStr()
{
    lineStr=$1
    CheckString "${lineStr}" "${xbasetype}${xstar0x}${xspace1x}${xvar};" "变量没有初始化"
    CheckString "${lineStr}" "${xfuncdef}" "[note]注意是否有入参检查"
}

function DoCheckFile()
{
    dest="$1.chk"
    hdrEn=$2
    srcEn=$3
    if [ -f ${dest} ]
    then
        rm -rf ${dest}
    fi
    lineCnt=1
    FileCnt=$((FileCnt+1))
    fileLineCnt=$(awk '{print NR}' $1 | tail -n1)
    echo "fileLineCnt=${fileLineCnt}"
    TotLineCount=$((TotLineCount+fileLineCnt))

    echo "check file: ${fileName}"
    cat $1 | while read line
    do
        ErrMsg=""
        newStr=${line}
        # echo $lineStr
        # echo $newStr
        # RemoveInvalidPatchString "${newStr}"
        # RemoveComment "${newStr}"
        # echo $newStr

        if [ "x${newStr}" != "x" ]
        then
            echo "${lineCnt}: "${newStr}
        fi

        # CheckAllStr "${line}"
        CheckAllStr "${newStr}"
        if [ x"hdrEn" == "xyes" ]
        then
            CheckHeaderStr "${line}"
        fi
        if [ x"srcEn" == "xyes" ]
        then
            CheckSourceStr "${line}"
        fi
        if [ x${ErrMsg} != "x" ]
        then
            tmpMsg="  //FIXME:${ErrMsg}"
            ErrMsg=${tmpMsg}
            echo  "${line}""${ErrMsg}" >> ${dest}
        else
            echo  "${line}" >> ${dest}
        fi
        lineCnt=$((lineCnt + 1))
    done
# mv ${dest} ${fileName}
}

function DoCheckSrcOrHdrFile()
{
    fileName=$1
    suffix=${fileName##*.}
    echo "fileName=${fileName}"
    echo "suffix=${suffix}"

    bakFileName=${fileName}.bak
    cp -rf ${fileName} ${bakFileName}
    sed -i s/\\\\/\\\\\\\\/g ${bakFileName}
    if [ x${suffix} == "xh" ]
    then
        DoCheckFile ${bakFileName} "yes" "no"
    fi
    if [ x${suffix} == "xcpp" ]
    then
        DoCheckFile ${bakFileName} "no" "yes"
    fi
    if [ x${IsPatch} == "xy" ]
    then
        DoCheckFile ${bakFileName} "yes" "yes"
    fi
    rm -rf ${bakFileName}
}

function CheckFileOrDir()
{
    op=$1
    fileName=$2
    if [ "$op" == "-s" ]
    then
        DoCheckSrcOrHdrFile ${fileName}
    fi

    if [ "$op" == "-p" ]
    then
        IsPatch=y
        DoCheckSrcOrHdrFile ${fileName}
    fi

    if [ "$op" == "-d" ]
    then
        filelist=`ls ${fileName}`
        for file in $filelist
        do
            DoCheckSrcOrHdrFile $file
        done
    fi
}

function usage()
{
    echo "mark_files.sh usage:"
    echo "mark_files.sh <op> <file | dir>"
    echo "op : -d, -s or -p"
    echo "-d : check directory"
    echo "-s : check single source file"
    echo "-p : check patch file"
}

function CheckArgs()
{
    if [ $# -lt 1 ]
    then
        usage
        exit
    fi
    if [ "$1" == "-h" -o "$1" == "--help" ]
    then
        usage
        exit
    fi
    if [ $# -lt 2 ]
    then
        usage
        exit
    fi
}

function main()
{
    CheckArgs $@
    start_time=$(date "+%Y-%m-%d %H:%M:%S")
    IFS_OLD=$IFS
    IFS=$'\n'
    CheckFileOrDir $@
    IFS=$IFS_OLD
    end_time=$(date "+%Y-%m-%d %H:%M:%S")
    start_seconds=$(date --date="$start_time" +%s)
    end_seconds=$(date --date="$end_time" +%s)
    timediff=$((end_seconds-start_seconds))
    speed=$((TotLineCount/timediff))
    echo "start_time   : $start_time"
    echo "end_time     : $end_time"
    echo "file_cnt     : $FileCnt"
    echo "TotLineCount : $TotLineCount"
    echo "MarkSpeed    : ${speed}"
}
main $@
